package com.wang.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wang.enums.BizCodeEnum;
import com.wang.enums.CouponCategoryEnum;
import com.wang.enums.CouponPublishEnum;
import com.wang.enums.CouponUserStateEnum;
import com.wang.exception.BizException;
import com.wang.interceptor.LoginInterceptor;
import com.wang.mapper.CouponRecordMapper;
import com.wang.model.CouponDO;
import com.wang.mapper.CouponMapper;
import com.wang.model.CouponRecordDO;
import com.wang.model.LoginUser;
import com.wang.request.NewUserCouponRequest;
import com.wang.service.CouponService;
import com.wang.util.JsonData;
import com.wang.vo.CouponVO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 落日孤城
 * @since 2025-05-10
 */
@Service
@Slf4j
public class CouponServiceImpl  implements CouponService {
    @Autowired
    private CouponMapper couponMapper;
    
    @Autowired
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分页查询优惠券活动
     * @param page
     * @param size
     * @return
     */
    @Override
    public Map<String, Object> pageCouponActivity(Integer page, Integer size) {
        Page<CouponDO> pageInfo = new Page<>(page, size);

        IPage<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
                .eq("publish", CouponPublishEnum.PUBLISH.name())
                .eq("category", CouponCategoryEnum.PROMOTION.name())
                .orderByDesc("create_time"));

        Map<String, Object> pageMap = new HashMap<>();
        pageMap.put("total_record", couponDOPage.getTotal());
        pageMap.put("total_page", couponDOPage.getPages());
        pageMap.put("current_data", couponDOPage.getRecords().stream().map(obj -> beanProcess(obj)).collect(Collectors.toList()));

        return pageMap;
    }

    /**
     * 领取优惠券
     * 1.  判断优惠券是否可用
     *       1.1 优惠券状态是否可用（检查优惠券的状态是不是：发布状态 ）
     *       1.2 优惠券是否过期
     *       1.3 优惠券是否还有库存
     *       1.4 优惠券是否超过用户领取限制（查询用户之前领取了多少对应的优惠券，判断是否可以再领取）
     *       1.5 优惠券是否存在
     *2.减少对应的优惠券库存  成功后再进行下面一步，不然抛出一个异常出去
     *3.添加用户领取的优惠券记录
     *
     * @param couponId
     * @param couponCategoryEnum
     * @return
     */
    @Transactional(rollbackFor=Exception.class,propagation= Propagation.REQUIRED)
    @Override
    public JsonData addCoupon(Long couponId, CouponCategoryEnum couponCategoryEnum) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();//获取用户id

        //获取锁  使用redisson进行分布式锁
        String lockKey = "coupon:"+couponId;
        RLock lock = redissonClient.getLock(lockKey);
        //多个线程会进行阻塞，等待锁的释放
        //不添加过期时间，有watch dog（看门狗）机制，会自动续时
        //添加过期时间，到了时间就会自动解锁（这就没有了看门狗机制，不会进行自动续时）
        lock.lock();
        //lock.lock(10, TimeUnit.SECONDS);   添加了过期时间的案例
        log.info("加锁成功：{}" + Thread.currentThread().getId());
        try{
            //查询对应的优惠券信息  根据优惠券的id、优惠券的类型、优惠券的状态进行查找   这里已经进行了状态的判断
            CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
                    .eq("id", couponId)
                    .eq("category", couponCategoryEnum.name())
                    .eq("publish", CouponPublishEnum.PUBLISH.name()));

            //优惠券校验
            couponCheck(couponDO, loginUser.getId());

            //删除库存  TODO
            int rows = couponMapper.reduceStock(couponId,couponDO.getStock());


            //添加优惠券领取记录
            CouponRecordDO couponRecordDO = new CouponRecordDO();
            BeanUtils.copyProperties(couponDO, couponRecordDO);
            couponRecordDO.setUserId(loginUser.getId());
            couponRecordDO.setUserName(loginUser.getName());
            couponRecordDO.setCreateTime(new Date());
            couponRecordDO.setId(null);//这里要设置为空，因为在上面的操作中couponDO的id被赋值给了couponRecordDO
            couponRecordDO.setCouponId(couponDO.getId());
            couponRecordDO.setUseState(CouponUserStateEnum.USED.name());

            //判断库存是否减少成功
            if(rows == 1){
                couponRecordMapper.insert(couponRecordDO);
            }else{
                log.warn("发放优惠券失败:{},用户:{}",couponDO,loginUser);
                //下面这一步我感觉异常信息不是这个，
                // 因为在优惠券校验的时候已经进行了处理，
                // 所以不可能是下面的原因导致的优惠券发放失败
                //纠正：可能是下面的异常信息
                // 因为高并发下，可能会出现一开始数据库信息还没更新成功，后面更新成功，导致库存减少失败的情况（数据库）
                //也就是一开始通过了checkCoupon方法中库存校验，但是后面数据库信息更新好了（小于等于0），导致库存减少失败
                throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
            }

        }
        finally {
            log.info("解锁成功：{}" + Thread.currentThread().getId());
            lock.unlock();
        }
        return JsonData.buildSuccess();
    }

    /**
     * RPC-新用户注册领取新人优惠券接口
     *
     * 1.用户注册时就发放，所以没有token(所以要求修改拦截配置，不让该接口被拦截)
     * 2.调用优惠券发放接口，需要token(所以本接口需要与LoginInterceptor.threadLocal进行绑定)
     * @param newUserCouponRequest
     * @return
     */
    @Transactional(rollbackFor=Exception.class,propagation= Propagation.REQUIRED)
    @Override
    public JsonData initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        //查看新人优惠券有哪些
        List<CouponDO> couponDOList = couponMapper.selectList(new QueryWrapper<CouponDO>()
                .eq("category", CouponCategoryEnum.NEW_USER.name()));

        //创建LoginUser并与LoginInterceptor.threadLocal进行绑定
        LoginUser loginUser = LoginUser.builder()
                .id(newUserCouponRequest.getUserID())
                .name(newUserCouponRequest.getName())
                .build();

        LoginInterceptor.threadLocal.set(loginUser);

        //本地调用接口，进行优惠券的发放
        for(CouponDO couponDO : couponDOList){
            //幂等操作，需要加锁
            this.addCoupon(couponDO.getId(), CouponCategoryEnum.NEW_USER);
        }
        return JsonData.buildSuccess();
    }

    /**
     * 优惠券校验
     * @param couponDO
     * @param userId
     */
    private void couponCheck(CouponDO couponDO, Long userId) {
        //判断优惠券是否存在
        if(couponDO == null){
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }

        //判断优惠券是否过期
        Long startTime = couponDO.getStartTime().getTime();
        long endTime = couponDO.getEndTime().getTime();
        long nowTime = System.currentTimeMillis();
        if(startTime > nowTime || endTime < nowTime){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }
        
        //判断优惠券是否还有库存
        if(couponDO.getStock() <= 0){
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }

        //判断用户之前领取该优惠券的次数
        Integer count = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>()
                .eq("user_id", userId)
                .eq("coupon_id", couponDO.getId()));

        if(count >= couponDO.getUserLimit()){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }

    }

    /**
     * 将CouponDO类转化为CouponVO类
     * @param couponDO
     * @return
     */
    private CouponVO beanProcess(CouponDO couponDO) {
        CouponVO couponVO = new CouponVO();
        BeanUtils.copyProperties(couponDO,couponVO);
        return couponVO;
    }
}
